package com.seadragon.app.ksr.service.impl;

import static com.seadragon.app.ksr.model.KsrConstants.CUSTOMER_SEARCH_TYPE;
import static com.seadragon.app.ksr.model.KsrConstants.EVENT_SEARCH_TYPE;
import static com.seadragon.app.ksr.model.KsrConstants.OFFICE_SEARCH_TYPE;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolFilterBuilder;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.index.query.FilteredQueryBuilder;
import org.elasticsearch.index.query.GeoShapeFilterBuilder;
import org.elasticsearch.index.query.GeoShapeQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.seadragon.app.ksr.dao.StateDao;
import com.seadragon.app.ksr.json.gson.MultiPolygonAdapter;
import com.seadragon.app.ksr.json.gson.PointAdapter;
import com.seadragon.app.ksr.model.Address;
import com.seadragon.app.ksr.model.AddressType;
import com.seadragon.app.ksr.model.Customer;
import com.seadragon.app.ksr.model.GeocodedAddress;
import com.seadragon.app.ksr.model.Office;
import com.seadragon.app.ksr.model.SearchParams;
import com.seadragon.app.ksr.model.SearchResult;
import com.seadragon.app.ksr.model.SortType;
import com.seadragon.app.ksr.model.tiger.County;
import com.seadragon.app.ksr.model.tiger.Location;
import com.seadragon.app.ksr.model.tiger.LocationFactory;
import com.seadragon.app.ksr.model.tiger.Place;
import com.seadragon.app.ksr.model.tiger.State;
import com.seadragon.app.ksr.model.tiger.Zip;
import com.seadragon.app.ksr.service.AddressService;
import com.seadragon.app.ksr.service.GeocodeService;
import com.seadragon.app.ksr.service.SearchService;
import com.seadragon.app.ksr.util.CacheManager;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;

@Service("searchService")
public class SearchServiceESJavaImpl implements SearchService {
	private Logger logger = LoggerFactory.getLogger(SearchServiceESJavaImpl.class);

	@Autowired
	private Client client;

	@Autowired
	private AddressService addressService;

	@Autowired
	private GeocodeService geocodeService;

	@Autowired
	private StateDao stateDao;

	private Gson gson;
	@Autowired
	private CacheManager cacheManager;
	public SearchServiceESJavaImpl() {
		GsonBuilder gsonBuilder = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
		gsonBuilder.registerTypeAdapter(Point.class, new PointAdapter());
		gsonBuilder.registerTypeAdapter(MultiPolygon.class, new MultiPolygonAdapter());
		gson = gsonBuilder.create();
//		Settings settings = ImmutableSettings.settingsBuilder().put("cluster.name", "elasticsearch").build();
//		client = new TransportClient(settings);
//		client.addTransportAddress(new InetSocketTransportAddress("127.0.0.1", 9300));
//		client.addTransportAddress(new InetSocketTransportAddress("yadong.dyndns-web.com", 9300));
	}

	private QueryBuilder createOfficeKeywordQuery(String keyword){
		if (keyword == null || keyword.length() == 0) {
			return QueryBuilders.matchAllQuery();
		} else {
			return QueryBuilders.boolQuery()
					.should(QueryBuilders.multiMatchQuery(keyword, "description", "specialty", "business.description", "business.specialty"))
					.should(QueryBuilders.nestedQuery("business.categories", QueryBuilders.termQuery("business.categories.name", keyword)));
		}
	}

	private QueryBuilder createEventKeywordQuery(String keyword){
		if (keyword == null || keyword.length() == 0) {
			return QueryBuilders.matchAllQuery();
		} else {
			return QueryBuilders.boolQuery()
					.should(QueryBuilders.multiMatchQuery(keyword, "description", "eventName", "notes"))
					.should(QueryBuilders.nestedQuery("categories", QueryBuilders.termQuery("categories.name", keyword)));
		}
	}

	private FilterBuilder buildAddressFilter(GeocodedAddress address, int distance) {
		if (AddressType.ROUTE == address.getAddressType()) {
			return FilterBuilders.geoDistanceFilter("address.location")
		    .point(address.getLatitude(), address.getLongitude())
		    .distance(distance, DistanceUnit.MILES)
		    .optimizeBbox("memory")                    // Can be also "indexed" or "none"
		    .geoDistance(GeoDistance.PLANE); 
		} else if (AddressType.ZIP == address.getAddressType()) {
			return FilterBuilders.termFilter("address.geocodedZip", address.getZip());
		} else if (AddressType.CITY == address.getAddressType()) {
			return FilterBuilders.boolFilter().must(FilterBuilders.termFilter("address.geocodedCity", address.getCity()))
			.must(FilterBuilders.termFilter("address.geocodedState", address.getState()));
		} else if (AddressType.COUNTY == address.getAddressType()) {
			return FilterBuilders.boolFilter().must(FilterBuilders.termFilter("address.geocodedCounty", address.getCounty()))
			.must(FilterBuilders.termFilter("address.geocodedState", address.getState()));
		} else if (AddressType.STATE == address.getAddressType()) {
			return FilterBuilders.termFilter("address.geocodedState", address.getState());
		}
		return null;
	}

	private FilterBuilder buildLocationFilter(Location location, int distance) {
		if (AddressType.ROUTE == location.getLocationType()) {
			return FilterBuilders.geoDistanceFilter("address.location")
		    .point(location.getLatitude(), location.getLongitude())
		    .distance(distance, DistanceUnit.MILES)
		    .optimizeBbox("memory")                    // Can be also "indexed" or "none"
		    .geoDistance(GeoDistance.PLANE); 
		} else if (AddressType.ZIP == location.getLocationType()) {
			return FilterBuilders.termFilter("address.geocodedZip", location.getName());
		} else if (AddressType.CITY == location.getLocationType()) {
			return FilterBuilders.boolFilter().must(FilterBuilders.termFilter("address.geocodedCity", location.getName()))
			.must(FilterBuilders.termFilter("address.geocodedState", location.getState()));
		} else if (AddressType.COUNTY == location.getLocationType()) {
			return FilterBuilders.boolFilter().must(FilterBuilders.termFilter("address.geocodedCounty", location.getName()))
			.must(FilterBuilders.termFilter("address.geocodedState", location.getState()));
		} else if (AddressType.STATE == location.getLocationType()) {
			return FilterBuilders.termFilter("address.geocodedState", location.getState());
		}
		return null;
	}
	
	private FilterBuilder buildAddressFilter(Location targetLocation, List<? extends Location> neighbors, int distance) {
		FilterBuilder targetLocationFilterBuilder = buildLocationFilter(targetLocation, distance);
		if (neighbors != null) {
			BoolFilterBuilder neighborsFilterBuilder = FilterBuilders.boolFilter();
			for (Location location : neighbors) {
				neighborsFilterBuilder.should(buildLocationFilter(location, distance));
			}
			return FilterBuilders.boolFilter().should(targetLocationFilterBuilder).should(neighborsFilterBuilder);
		}

		return targetLocationFilterBuilder;
	}

	private FilterBuilder buildRatingFilter(Integer ratingFilter) {
		return FilterBuilders.rangeFilter("rating").gte(ratingFilter.intValue());
	}

	private FilterBuilder buildPriceFilter(List<Integer> priceFilter) {
		return FilterBuilders.inFilter("priceRange", priceFilter.toArray());
	}

	private FilterBuilder buildAgeFilter(int from, int to) {
		Calendar dayFrom = Calendar.getInstance();
		dayFrom.add(Calendar.MONTH, -1*from);
		Calendar dayTo = Calendar.getInstance();
		dayTo.add(Calendar.MONTH, -1*to+12);
		
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		return FilterBuilders.rangeFilter("dateOfBirth").from(sdf.format(dayFrom.getTime())).to(sdf.format(dayTo.getTime()));
	}

	
	@Override
	public SearchResult search(SearchParams params) {
		if (params != null) {
			if(params.getIndexType().equalsIgnoreCase(OFFICE_SEARCH_TYPE)){
				return searchOffice(params);
			}else if(params.getIndexType().equalsIgnoreCase(CUSTOMER_SEARCH_TYPE)){
				return searchCustomer(params);
			}else if(params.getIndexType().equalsIgnoreCase(EVENT_SEARCH_TYPE)){
				return searchEvent(params);
			}
		}
		return null;
	}

	private SearchResult searchCustomer(SearchParams params){
		QueryBuilder keywordQueryBuilder = createCustomerKeywordQuery(params.getKeyword());

		GeocodedAddress geocodedAddress = null;
		String location = params.getLocation();
		if (location != null && location.length() > 0) {
			geocodedAddress = geocodeService.geocode(location);
		}
		FilterBuilder addressFilterBuilder = null;
		if (geocodedAddress != null) {
			addressFilterBuilder =  buildAddressFilter(geocodedAddress, params.getDistance() == null ? 20 : params.getDistance().intValue());
		}
		Location target = params.getTargetLocation();
		List<? extends Location> neighborsFilter = params.getNeighbors();
		FilterBuilder locationFilterBuilder = null;
		if (target != null) {
			locationFilterBuilder = buildAddressFilter(target, neighborsFilter, params.getDistance() == null ? 20 : params
					.getDistance().intValue());
		}

		FilterBuilder ageFilterBuilder = null;
		if (params.getAgeFilterFrom() != params.getAgeFilterTo()) {
			ageFilterBuilder = buildAgeFilter( params.getAgeFilterTo(), params.getAgeFilterFrom());
		}
		int nfilters = 0;
		BoolFilterBuilder filterBuilder  = FilterBuilders.boolFilter();
		FilterBuilder finalFilterBuilder  = null;;
		if (locationFilterBuilder != null) {
			nfilters++;
			filterBuilder.must(locationFilterBuilder);
			finalFilterBuilder = locationFilterBuilder;
		} else if (addressFilterBuilder != null) {
			filterBuilder.must(addressFilterBuilder);
			finalFilterBuilder = addressFilterBuilder;
			nfilters++;
		}
		if (ageFilterBuilder != null) {
			filterBuilder.must(ageFilterBuilder);
			finalFilterBuilder = ageFilterBuilder;
			nfilters++;
		}
		SearchResult searchResult = new SearchResult();
		findNeighborsByES(searchResult, params.getTargetLocation(), params.getLocation(), geocodedAddress);
		
		FilteredQueryBuilder queryBuilder = null;
		if (nfilters == 1){
			queryBuilder = QueryBuilders.filteredQuery(keywordQueryBuilder, finalFilterBuilder);
		}else if(nfilters > 1){
			queryBuilder = QueryBuilders.filteredQuery(keywordQueryBuilder, filterBuilder);				
		}
		SearchRequestBuilder srb = client.prepareSearch(ksrIndexName())
		        .setTypes(CUSTOMER_SEARCH_TYPE)
		        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		        .setQuery(queryBuilder);
		
		int from = params.getDocsPerPage()*(params.getCurrentPage()-1);
		srb.setFrom(from).setSize(params.getDocsPerPage());

		SortType sortType = params.getSortType();
		if (SortType.DISTANCE == sortType) {
			if (geocodedAddress != null && AddressType.ROUTE == geocodedAddress.getAddressType()) {
				srb.addSort(SortBuilders.geoDistanceSort("address.location").geoDistance( 
		                GeoDistance.ARC).point(geocodedAddress.getPos().getY(), geocodedAddress.getPos().getX()));
			}
		} else if (SortType.AGE_ASC == sortType) {
			srb.addSort("dateOfBirth", SortOrder.DESC);
		} else if (SortType.AGE_DESC == sortType) {
			srb.addSort("dateOfBirth", SortOrder.ASC);
		} 
		
		logger.info(srb.toString());
		SearchResponse response = srb.execute().actionGet();
		List<Customer> matchedCustomers = new ArrayList<Customer>();
		for(SearchHit sh : response.getHits()){
			matchedCustomers.add(gson.fromJson(sh.getSourceAsString(), Customer.class));
		}
		searchResult.setCustomers(matchedCustomers);
		logger.info(response.toString());
		return searchResult;

	}
	
	private QueryBuilder createCustomerKeywordQuery(String keyword){
		if (keyword == null || keyword.length() == 0) {
			return QueryBuilders.matchAllQuery();
		} else {
			return QueryBuilders.matchQuery("interest", keyword);
		}
	}

	private SearchResult searchOffice(SearchParams params){
		QueryBuilder keywordQueryBuilder = createOfficeKeywordQuery(params.getKeyword());

		GeocodedAddress geocodedAddress = null;
		String location = params.getLocation();
		if (location != null && location.length() > 0) {
			geocodedAddress = geocodeService.geocode(location);
		}
		FilterBuilder addressFilterBuilder = null;
		if (geocodedAddress != null) {
			addressFilterBuilder =  buildAddressFilter(geocodedAddress, params.getDistance() == null ? 20 : params.getDistance().intValue());
		}
		FilterBuilder priceFilterBuilder = null;
		List<Integer> priceFilters = params.getPriceFilters();
		if (priceFilters != null) {
			priceFilterBuilder = buildPriceFilter(priceFilters);
		}

		FilterBuilder ratingFilterBuilder = null;
		Integer ratingFilter = params.getRatingFilter();
		if (ratingFilter != null) {
			ratingFilterBuilder = buildRatingFilter( ratingFilter);
		}

		Location target = params.getTargetLocation();
		List<? extends Location> neighborsFilter = params.getNeighbors();
		FilterBuilder locationFilterBuilder = null;
		if (target != null) {
			locationFilterBuilder = buildAddressFilter(target, neighborsFilter, params.getDistance() == null ? 20 : params
					.getDistance().intValue());
		}
		int nfilters = 0;
		BoolFilterBuilder filterBuilder  = FilterBuilders.boolFilter();
		FilterBuilder finalFilterBuilder  = null;;
		if (locationFilterBuilder != null) {
			nfilters++;
			filterBuilder.must(locationFilterBuilder);
			finalFilterBuilder = locationFilterBuilder;
		} else if (addressFilterBuilder != null) {
			filterBuilder.must(addressFilterBuilder);
			finalFilterBuilder = addressFilterBuilder;
			nfilters++;
		}
		if (priceFilterBuilder != null) {
			filterBuilder.must(priceFilterBuilder);
			finalFilterBuilder = priceFilterBuilder;
			nfilters++;
		}
		if (ratingFilterBuilder != null) {
			filterBuilder.must(ratingFilterBuilder);
			finalFilterBuilder = ratingFilterBuilder;
			nfilters++;
		}

		SearchResult searchResult = new SearchResult();
		findNeighborsByES(searchResult, params.getTargetLocation(), params.getLocation(), geocodedAddress);
		QueryBuilder queryBuilder = null;
		if (nfilters == 0){
			queryBuilder = keywordQueryBuilder;
		}else if (nfilters == 1){
			queryBuilder = QueryBuilders.filteredQuery(keywordQueryBuilder, finalFilterBuilder);
		}else if(nfilters > 1){
			queryBuilder = QueryBuilders.filteredQuery(keywordQueryBuilder, filterBuilder);				
		}

		SearchRequestBuilder srb = client.prepareSearch(ksrIndexName())
		        .setTypes("office")
		        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		        .setQuery(queryBuilder);

		int from = params.getDocsPerPage()*(params.getCurrentPage()-1);
		srb.setFrom(from).setSize(params.getDocsPerPage());

		SortType sortType = params.getSortType();
		if (SortType.DISTANCE == sortType) {
			if (geocodedAddress != null && AddressType.ROUTE == geocodedAddress.getAddressType()) {
				srb.addSort(SortBuilders.geoDistanceSort("address.location").geoDistance( 
		                GeoDistance.ARC).point(geocodedAddress.getPos().getY(), geocodedAddress.getPos().getX()));
			}
		} else if (SortType.PRICE_ASC == sortType) {
			srb.addSort("price", SortOrder.ASC);
		} else if (SortType.PRICE_DSC == sortType) {
			srb.addSort("price", SortOrder.DESC);
		} else if (SortType.RATING == sortType) {
			srb.addSort("rating", SortOrder.DESC);
		} else if (SortType.MOST_REVIEWED == sortType) {
			srb.addSort("reviewCount", SortOrder.DESC);
		}
		
		logger.info(srb.toString());
		SearchResponse response = srb.execute().actionGet();
		if(response.getHits() != null){
			searchResult.setTotalResultNumber(response.getHits().getTotalHits());
		}
		List<Office> matchedOffices = new ArrayList<Office>();
		for(SearchHit sh : response.getHits()){
			matchedOffices.add(gson.fromJson(sh.getSourceAsString(), Office.class));
		}
		searchResult.setOffices(matchedOffices);
		logger.info(response.toString());
		return searchResult;
	}
	
	private SearchResult searchEvent(SearchParams params){
		QueryBuilder keywordQueryBuilder = createEventKeywordQuery(params.getKeyword());

		GeocodedAddress geocodedAddress = null;
		String location = params.getLocation();
		if (location != null && location.length() > 0) {
			geocodedAddress = geocodeService.geocode(location);
		}
		FilterBuilder addressFilterBuilder = null;
		if (geocodedAddress != null) {
			addressFilterBuilder =  buildAddressFilter(geocodedAddress, params.getDistance() == null ? 20 : params.getDistance().intValue());
		}
		FilterBuilder priceFilterBuilder = null;
		List<Integer> priceFilters = params.getPriceFilters();
		if (priceFilters != null) {
			priceFilterBuilder = buildPriceFilter(priceFilters);
		}

		Location target = params.getTargetLocation();
		List<? extends Location> neighborsFilter = params.getNeighbors();
		FilterBuilder locationFilterBuilder = null;
		if (target != null) {
			locationFilterBuilder = buildAddressFilter(target, neighborsFilter, params.getDistance() == null ? 20 : params
					.getDistance().intValue());
		}
		int nfilters = 0;
		BoolFilterBuilder filterBuilder  = FilterBuilders.boolFilter();
		FilterBuilder finalFilterBuilder  = null;;
		if (locationFilterBuilder != null) {
			nfilters++;
			filterBuilder.must(locationFilterBuilder);
			finalFilterBuilder = locationFilterBuilder;
		} else if (addressFilterBuilder != null) {
			filterBuilder.must(addressFilterBuilder);
			finalFilterBuilder = addressFilterBuilder;
			nfilters++;
		}
		if (priceFilterBuilder != null) {
			filterBuilder.must(priceFilterBuilder);
			finalFilterBuilder = priceFilterBuilder;
			nfilters++;
		}

		SearchResult searchResult = new SearchResult();
		findNeighborsByES(searchResult, params.getTargetLocation(), params.getLocation(), geocodedAddress);
		QueryBuilder queryBuilder = null;
		if (nfilters == 0){
			queryBuilder = keywordQueryBuilder;
		}else if (nfilters == 1){
			queryBuilder = QueryBuilders.filteredQuery(keywordQueryBuilder, finalFilterBuilder);
		}else if(nfilters > 1){
			queryBuilder = QueryBuilders.filteredQuery(keywordQueryBuilder, filterBuilder);				
		}

		SearchRequestBuilder srb = client.prepareSearch(ksrIndexName())
		        .setTypes("event")
		        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		        .setQuery(queryBuilder);

		int from = params.getDocsPerPage()*(params.getCurrentPage()-1);
		srb.setFrom(from).setSize(params.getDocsPerPage());

		SortType sortType = params.getSortType();
		if (SortType.DISTANCE == sortType) {
			if (geocodedAddress != null && AddressType.ROUTE == geocodedAddress.getAddressType()) {
				srb.addSort(SortBuilders.geoDistanceSort("address.location").geoDistance( 
		                GeoDistance.ARC).point(geocodedAddress.getPos().getY(), geocodedAddress.getPos().getX()));
			}
		} else if (SortType.PRICE_ASC == sortType) {
			srb.addSort("price", SortOrder.ASC);
		} else if (SortType.PRICE_DSC == sortType) {
			srb.addSort("price", SortOrder.DESC);
		} else if (SortType.MOST_REVIEWED == sortType) {
			srb.addSort("reviewCount", SortOrder.DESC);
		}
		
		logger.info(srb.toString());
		SearchResponse response = srb.execute().actionGet();
		if(response.getHits() != null){
			searchResult.setTotalResultNumber(response.getHits().getTotalHits());
		}
		for(SearchHit sh : response.getHits()){
			searchResult.addResult(sh.getSourceAsString());
			
			
		}
		logger.info(response.toString());
		return searchResult;
	}
	
//	private void findNeighborsByPostgis(SearchResult searchResult, Location target, String inputLocation, GeocodedAddress geocodedAddress){
//		if(target != null){
//			if (AddressType.ZIP == target.getLocationType()) {
//				List<Zip> neighbors = addressService.findAdjacentZips(geocodedAddress.getZip());
//				for (Zip zip : neighbors) {
//					if (zip.getZip().equals(geocodedAddress.getZip())) {
//						searchResult.setTargetLocation(zip);
//						neighbors.remove(zip);
//						break;
//					}
//				}
//
//				searchResult.setLocationFilter(neighbors);
//			} else if (AddressType.CITY == target.getLocationType()) {
//				List<Place> neighbors = addressService.findAdjacentPlaces(geocodedAddress.getCity(),
//						stateDao.findStatefpByStateName(geocodedAddress.getState()));
//				for (Place place : neighbors) {
//					if (place.getName().equalsIgnoreCase(geocodedAddress.getCity())
//							&& cacheManager.getStateByStateFp(place.getStatefp()).equalsIgnoreCase(geocodedAddress.getState())) {
//						searchResult.setTargetLocation(place);
//						neighbors.remove(place);
//						break;
//					}
//				}
//				searchResult.setLocationFilter(neighbors);
//			} else if (AddressType.COUNTY == target.getLocationType()) {
//				List<County> neighbors = addressService.findAdjacentCounties(geocodedAddress.getCounty(),
//						stateDao.findStatefpByStateName(geocodedAddress.getState()));
//				for (County county : neighbors) {
//					if (county.getName().equalsIgnoreCase(geocodedAddress.getCounty())
//							&& cacheManager.getStateByStateFp(county.getStatefp()).equalsIgnoreCase(geocodedAddress.getState())) {
//						searchResult.setTargetLocation(county);
//						neighbors.remove(county);
//						break;
//					}
//				}
//				searchResult.setLocationFilter(neighbors);
//			} else if (AddressType.STATE == target.getLocationType()) {
//				List<State> neighbors = addressService.findAdjacentStates(geocodedAddress.getState());
//				for (State state : neighbors) {
//					if (state.getState().equalsIgnoreCase(geocodedAddress.getState())) {
//						searchResult.setTargetLocation(state);
//						neighbors.remove(state);
//						break;
//					}
//				}
//				searchResult.setLocationFilter(neighbors);
//			} else if (AddressType.ROUTE == target.getLocationType()) {
//				Location loc = LocationFactory.getInstance(inputLocation, AddressType.ROUTE, geocodedAddress.getState(),
//						geocodedAddress.getLatitude(), geocodedAddress.getLongitude());
//				searchResult.setTargetLocation(loc);
//			}
//		}else if (geocodedAddress != null) {
//			if (AddressType.ZIP == geocodedAddress.getAddressType()) {
//				List<Zip> neighbors = addressService.findAdjacentZips(geocodedAddress.getZip());
//				for (Zip zip : neighbors) {
//					if (zip.getZip().equals(geocodedAddress.getZip())) {
//						searchResult.setTargetLocation(zip);
//						neighbors.remove(zip);
//						break;
//					}
//				}
//
//				searchResult.setLocationFilter(neighbors);
//			} else if (AddressType.CITY == geocodedAddress.getAddressType()) {
//				List<Place> neighbors = addressService.findAdjacentPlaces(geocodedAddress.getCity(),
//						stateDao.findStatefpByStateName(geocodedAddress.getState()));
//				for (Place place : neighbors) {
//					if (place.getName().equalsIgnoreCase(geocodedAddress.getCity())
//							&& cacheManager.getStateByStateFp(place.getStatefp()).equalsIgnoreCase(geocodedAddress.getState())) {
//						searchResult.setTargetLocation(place);
//						neighbors.remove(place);
//						break;
//					}
//				}
//				searchResult.setLocationFilter(neighbors);
//			} else if (AddressType.COUNTY == geocodedAddress.getAddressType()) {
//				List<County> neighbors = addressService.findAdjacentCounties(geocodedAddress.getCounty(),
//						stateDao.findStatefpByStateName(geocodedAddress.getState()));
//				for (County county : neighbors) {
//					if (county.getName().equalsIgnoreCase(geocodedAddress.getCounty())
//							&& cacheManager.getStateByStateFp(county.getStatefp()).equalsIgnoreCase(geocodedAddress.getState())) {
//						searchResult.setTargetLocation(county);
//						neighbors.remove(county);
//						break;
//					}
//				}
//				searchResult.setLocationFilter(neighbors);
//			} else if (AddressType.STATE == geocodedAddress.getAddressType()) {
//				List<State> neighbors = addressService.findAdjacentStates(geocodedAddress.getState());
//				for (State state : neighbors) {
//					if (state.getState().equalsIgnoreCase(geocodedAddress.getState())) {
//						searchResult.setTargetLocation(state);
//						neighbors.remove(state);
//						break;
//					}
//				}
//				searchResult.setLocationFilter(neighbors);
//			} else if (AddressType.ROUTE == geocodedAddress.getAddressType()) {
//				Location loc = LocationFactory.getInstance(inputLocation, AddressType.ROUTE, geocodedAddress.getState(),
//						geocodedAddress.getLatitude(), geocodedAddress.getLongitude());
//				searchResult.setTargetLocation(loc);
//			}
//		}
//	}
	
	private void findNeighborsByES(SearchResult searchResult, Location target, String inputLocation, GeocodedAddress geocodedAddress){
		if(target != null){
			findNeighborsByES(searchResult, target, inputLocation);
		}else if(geocodedAddress != null){
			findNeighborsByES(searchResult, inputLocation, geocodedAddress);
		}
	}
	
	private void findNeighborsByES(SearchResult searchResult, Location target, String inputLocation) {
		if (target == null)
			return;
		if (AddressType.ZIP == target.getLocationType()) {
			List<Zip> neighbors = searchNeighborsByZip(target.getName());
			for (Zip zip : neighbors) {
				if (zip.getGeoid().equals(target.getName())) {
					searchResult.setTargetLocation(zip);
					neighbors.remove(zip);
					break;
				}
			}

			searchResult.setLocationFilter(neighbors);
		} else if (AddressType.CITY == target.getLocationType()) {
			List<Place> neighbors = searchNeighborsByPlace(target.getGeoid());
			for (Place place : neighbors) {
				if (place.getName().equalsIgnoreCase(target.getName()) && place.getState().equalsIgnoreCase(target.getState())) {
					searchResult.setTargetLocation(place);
					neighbors.remove(place);
					break;
				}
			}
			searchResult.setLocationFilter(neighbors);
		} else if (AddressType.COUNTY == target.getLocationType()) {
			List<County> neighbors = searchNeighborsByCounty(target.getGeoid());

			for (County county : neighbors) {
				if (county.getGeoid().equalsIgnoreCase(target.getGeoid())) {
					searchResult.setTargetLocation(county);
					neighbors.remove(county);
					break;
				}
			}
			searchResult.setLocationFilter(neighbors);
		} else if (AddressType.STATE == target.getLocationType()) {
			List<State> neighbors = searchNeighborsByState(target.getGeoid());
			for (State state : neighbors) {
				if (state.getGeoid().equalsIgnoreCase(target.getGeoid())) {
					searchResult.setTargetLocation(state);
					neighbors.remove(state);
					break;
				}
			}
			searchResult.setLocationFilter(neighbors);
		} else if (AddressType.ROUTE == target.getLocationType()) {
			Location loc = LocationFactory.getInstance(null, inputLocation, AddressType.ROUTE, target.getState(), target.getLatitude(),
					target.getLongitude());
			searchResult.setTargetLocation(loc);
		}
	}

	private void findNeighborsByES(SearchResult searchResult, String inputLocation, GeocodedAddress geocodedAddress) {
		if (geocodedAddress == null)
			return;
		Location target = null;
		if (AddressType.ZIP == geocodedAddress.getAddressType()) {
			target = new Zip(geocodedAddress.getZip());
		} else if (AddressType.CITY == geocodedAddress.getAddressType()) {
			target = searchPlaceByPlaceAndState(geocodedAddress.getCity(), geocodedAddress.getState());
		} else if (AddressType.COUNTY == geocodedAddress.getAddressType()) {
			target = searchCountyByCountyAndState(geocodedAddress.getCounty(), geocodedAddress.getState());
		} else if (AddressType.STATE == geocodedAddress.getAddressType()) {
			target = searchStateByStusps(geocodedAddress.getState());
		} else if (AddressType.ROUTE == geocodedAddress.getAddressType()) {
			target = LocationFactory.getInstance(null, inputLocation, AddressType.ROUTE, geocodedAddress.getState(), geocodedAddress.getLatitude(),
					geocodedAddress.getLongitude());
		}
		findNeighborsByES(searchResult, target, inputLocation);
	}
	
	
	
	@Override
	public Address searchAddress(Address address) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void reindexOffice() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void reindexAddress() {
		// TODO Auto-generated method stub
		
	}
	
	public String tigerIndexName(){
		return "tiger";
	}
	
	public String ksrIndexName(){
		return "ksr";
	}

	@Override
	public List<State> searchNeighborsByState(String stateGeoid){
		GeoShapeFilterBuilder gsfb = FilterBuilders.geoShapeFilter("geom", stateGeoid, "state", ShapeRelation.INTERSECTS).indexedShapePath("geom").indexedShapeIndex(tigerIndexName()).filterName("geom");
		FilteredQueryBuilder fqb = QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), gsfb);
		SearchResponse response = client.prepareSearch(tigerIndexName())
		        .setTypes("state")
		        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		        .setQuery(fqb).execute().actionGet();
		List<State> matchedStates = new ArrayList<State>();
		for(SearchHit sh : response.getHits()){
//			State state = new State();
//			state.setStusps((String)(sh.getFields().get("stusps").getValue()));
//			state.setGeoid((String)(sh.getFields().get("geoid").getValue()));
//			state.setName((String)(sh.getFields().get("name").getValue()));
			matchedStates.add(gson.fromJson(sh.getSourceAsString(), State.class));
		}
		return matchedStates;
	}
	
	@Override
	public List<County> searchNeighborsByCounty(String countyGeoid){
		GeoShapeFilterBuilder gsfb = FilterBuilders.geoIntersectionFilter("geom", countyGeoid, "county").indexedShapePath("geom").indexedShapeIndex(tigerIndexName()).filterName("geom");
		FilteredQueryBuilder fqb = QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), gsfb);
		SearchResponse response = client.prepareSearch(tigerIndexName())
		        .setTypes("county")
		        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		        .setQuery(fqb).execute().actionGet();
		List<County> matchedCounties = new ArrayList<County>();
		for(SearchHit sh : response.getHits()){
//			County county = new County();
//			county.setCountyfp((String)(sh.getFields().get("countyfp").getValue()));
//			county.setGeoid((String)(sh.getFields().get("geoid").getValue()));
//			county.setName((String)(sh.getFields().get("name").getValue()));
//			county.setState((String)(sh.getFields().get("state").getValue()));
			matchedCounties.add(gson.fromJson(sh.getSourceAsString(), County.class));
		}
		return matchedCounties;
	}
	
	@Override
	public List<Place> searchNeighborsByPlace(String placeGeoid){
		GeoShapeFilterBuilder gsfb = FilterBuilders.geoShapeFilter("geom", placeGeoid, "place", ShapeRelation.INTERSECTS).indexedShapePath("geom").indexedShapeIndex(tigerIndexName()).filterName("geom");
		FilteredQueryBuilder fqb = QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), gsfb);
		SearchResponse response = client.prepareSearch(tigerIndexName())
		        .setTypes("place")
		        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		        .setQuery(fqb).execute().actionGet();
		List<Place> matchedPlaces = new ArrayList<Place>();
		for(SearchHit sh : response.getHits()){
//			Place place = new Place();
//			place.setPlacefp((String)(sh.getFields().get("placefp").getValue()));
//			place.setGeoid((String)(sh.getFields().get("geoid").getValue()));
//			place.setName((String)(sh.getFields().get("name").getValue()));
//			place.setCounty((String)(sh.getFields().get("county").getValue()));
//			place.setState((String)(sh.getFields().get("state").getValue()));
			matchedPlaces.add(gson.fromJson(sh.getSourceAsString(), Place.class));
		}
		return matchedPlaces;
	}
	
	@Override
	public List<Zip> searchNeighborsByZip(String zipGeoid){
		GeoShapeFilterBuilder gsfb = FilterBuilders.geoShapeFilter("geom", zipGeoid, "zip", ShapeRelation.INTERSECTS).indexedShapePath("geom").indexedShapeIndex(tigerIndexName()).filterName("geom");
		FilteredQueryBuilder fqb = QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), gsfb);
		SearchResponse response = client.prepareSearch(tigerIndexName())
		        .setTypes("zip")
		        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		        .setQuery(fqb).execute().actionGet();
		List<Zip> matchedZips = new ArrayList<Zip>();
		for(SearchHit sh : response.getHits()){
//			Zip zip = new Zip();
//			zip.setZip((String)(sh.getFields().get("zip").getValue()));
//			zip.setGeoid((String)(sh.getFields().get("geoid").getValue()));
//			zip.setCounty((String)(sh.getFields().get("county").getValue()));
//			zip.setState((String)(sh.getFields().get("state").getValue()));
			Zip zip = gson.fromJson(sh.getSourceAsString(), Zip.class);
			zip.setZip(zip.getGeoid());
			matchedZips.add(zip);
		}
		return matchedZips;
	}

	public State searchStateByName(String name){
		SearchResponse response = client.prepareSearch(tigerIndexName())
		        .setTypes("state")
		        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		        .setQuery(QueryBuilders.termQuery("name", name)).addFields("geoid", "stusps", "name").execute().actionGet();
		State state = new State();
		if( response.getHits().getTotalHits() == 0) return null;
		SearchHit sh = response.getHits().getAt(0);
		state.setStusps((String)(sh.getFields().get("stusps").getValue()));
		state.setGeoid((String)(sh.getFields().get("geoid").getValue()));
		state.setName((String)(sh.getFields().get("name").getValue()));
		return state;
	}
	
	public State searchStateByStusps(String stusps){
		SearchResponse response = client.prepareSearch(tigerIndexName())
		        .setTypes("state")
		        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		        .setQuery(QueryBuilders.termQuery("stusps", stusps.toLowerCase())).addFields("geoid", "stusps", "name").execute().actionGet();
		State state = new State();
		if( response.getHits().getTotalHits() == 0) return null;
		SearchHit sh = response.getHits().getAt(0);
		state.setStusps((String)(sh.getFields().get("stusps").getValue()));
		state.setGeoid((String)(sh.getFields().get("geoid").getValue()));
		state.setName((String)(sh.getFields().get("name").getValue()));
		return state;
	}
	
	public State searchStateByLocation(double lat, double lon){
		GeoShapeQueryBuilder gsqb = QueryBuilders.geoShapeQuery("geom", ShapeBuilder.newPoint(lon, lat)).indexedShapeIndex(tigerIndexName()).indexedShapeIndex("geom");
		SearchResponse response = client.prepareSearch(tigerIndexName())
		        .setTypes("state")
		        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		        .setQuery(gsqb).addFields("geoid", "stusps", "name").execute().actionGet();
		State state = new State();
		if( response.getHits().getTotalHits() == 0) return null;
		SearchHit sh = response.getHits().getAt(0);
		state.setStusps((String)(sh.getFields().get("stusps").getValue()));
		state.setGeoid((String)(sh.getFields().get("geoid").getValue()));
		state.setName((String)(sh.getFields().get("name").getValue()));
		return state;
	}
	
	public County searchCountyByCountyAndState(String countyName, String stusps){
		BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("name", countyName)).must(QueryBuilders.matchQuery("state", stusps));
		SearchResponse response = client.prepareSearch(tigerIndexName())
		        .setTypes("county")
		        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		        .setQuery(queryBuilder).addFields("geoid", "countyfp", "name", "state").execute().actionGet();
		if(response.getHits().getTotalHits() > 0){
			SearchHit sh = response.getHits().getAt(0);
			County county = new County();
			county.setCountyfp((String)(sh.getFields().get("countyfp").getValue()));
			county.setGeoid((String)(sh.getFields().get("geoid").getValue()));
			county.setName((String)(sh.getFields().get("name").getValue()));
			if(sh.getFields().get("state") != null)
			county.setState((String)(sh.getFields().get("state").getValue()));
			return county;
		}
		return null;
	}
	
	public Place searchPlaceByPlaceAndState(String placeName, String stusps){
		BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("name", placeName)).must(QueryBuilders.matchQuery("state", stusps));
		SearchResponse response = client.prepareSearch(tigerIndexName())
		        .setTypes("place")
		        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		        .setQuery(queryBuilder).addFields("geoid", "name", "county", "state").execute().actionGet();
		if(response.getHits().getTotalHits() > 0){
			SearchHit sh = response.getHits().getAt(0);
			Place place= new Place();
			place.setGeoid((String)(sh.getFields().get("geoid").getValue()));
			place.setName((String)(sh.getFields().get("name").getValue()));
			if(sh.getFields().get("county") != null)
				place.setCounty((String)(sh.getFields().get("county").getValue()));
			if(sh.getFields().get("state") != null)
				place.setState((String)(sh.getFields().get("state").getValue()));
			return place;
		}
		return null;
	}
	
	public County searchCountyByLocation(double lat, double lon){
		GeoShapeQueryBuilder gsqb = QueryBuilders.geoShapeQuery("geom", ShapeBuilder.newPoint(lon, lat)).indexedShapeIndex(tigerIndexName()).indexedShapeIndex("geom");
		SearchResponse response = client.prepareSearch(tigerIndexName())
		        .setTypes("county")
		        .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
		        .setQuery(gsqb).addFields("geoid", "countyfp", "name", "state").execute().actionGet();
		if(response.getHits().getTotalHits() > 0){
			SearchHit sh = response.getHits().getAt(0);
			County county = new County();
			county.setCountyfp((String)(sh.getFields().get("countyfp").getValue()));
			county.setGeoid((String)(sh.getFields().get("geoid").getValue()));
			county.setName((String)(sh.getFields().get("name").getValue()));
			if(sh.getFields().get("state") != null)
			county.setState((String)(sh.getFields().get("state").getValue()));
			return county;
		}
		return null;
	}
	
	public State searchStateById(String stateId){
		GetResponse response = client.prepareGet(tigerIndexName(), "state", stateId).execute().actionGet();
		return gson.fromJson(response.getSourceAsString(), State.class);
	}
	
	public County searchCountyById(String countyId){
		GetResponse response = client.prepareGet(tigerIndexName(), "county", countyId).execute().actionGet();
		return gson.fromJson(response.getSourceAsString(), County.class);
	}
	
	public Place searchPlaceById(String placeId){
		GetResponse response = client.prepareGet(tigerIndexName(), "state", placeId).execute().actionGet();
		return gson.fromJson(response.getSourceAsString(), Place.class);
	}
	
	public Zip searchZipById(String zipId){
		GetResponse response = client.prepareGet(tigerIndexName(), "zip", zipId).execute().actionGet();
		return gson.fromJson(response.getSourceAsString(), Zip.class);
	}
}
